/** * WorthDoing.ai * Author: Simon-Pierre Boucher * Contact: contact@spboucher.ai * File: src/app/opportunity/[id]/page.tsx * Description: Opportunity report page — streamed report, scores with confidence, evidence, competitors, skeptic case. */ import Link from "next/link"; import { notFound } from "next/navigation"; import { and, asc, eq, inArray } from "drizzle-orm"; import { db } from "@/lib/db/client"; import { opportunities, opportunityScores, opportunityEvidence, opportunityCompetitors, competitors, evidence, sources, investigations, } from "@/lib/db/schema"; import { Markdown } from "@/components/wd/Markdown"; import { ScorePanel } from "@/components/wd/ScorePanel"; import { WorthScore } from "@/components/wd/OpportunityCard"; import { EvidenceKindBadge } from "@/components/wd/badges"; export const dynamic = "force-dynamic"; export default async function OpportunityPage({ params }: PageProps<"/opportunity/[id]">) { const { id } = await params; if (!/^[0-9a-f-]{36}$/i.test(id)) notFound(); const [opp] = await db.select().from(opportunities).where(eq(opportunities.id, id)); if (!opp) notFound(); const [inv] = await db.select().from(investigations).where(eq(investigations.id, opp.investigationId)); const scores = await db.select().from(opportunityScores).where(eq(opportunityScores.opportunityId, id)); const links = await db.select().from(opportunityEvidence).where(eq(opportunityEvidence.opportunityId, id)); const evidenceIds = links.map((l) => l.evidenceId); const evRows = evidenceIds.length ? await db .select({ id: evidence.id, kind: evidence.kind, quote: evidence.quote, summary: evidence.summary, strength: evidence.strength, sourceUrl: sources.canonicalUrl, sourceTitle: sources.title, sourceDomain: sources.domain, }) .from(evidence) .innerJoin(sources, eq(sources.id, evidence.sourceId)) .where(and(eq(evidence.investigationId, opp.investigationId), inArray(evidence.id, evidenceIds))) .orderBy(asc(evidence.createdAt)) : []; const comps = await db .select({ name: competitors.name, url: competitors.url, description: competitors.description, note: opportunityCompetitors.note, }) .from(opportunityCompetitors) .innerJoin(competitors, eq(competitors.id, opportunityCompetitors.competitorId)) .where(eq(opportunityCompetitors.opportunityId, id)); return (
{inv && ( ← investigation: {inv.objective} )}

{opp.title}

{opp.summary}

worth score

{opp.reportMd ? ( {opp.reportMd} ) : (

The report has not been synthesized yet.

)}

Evidence trail

    {evRows.map((e, i) => (
  1. [{i + 1}]
    strength {Math.round(e.strength * 100)}%
    “{e.quote}”

    {e.summary}

    {e.sourceTitle ?? e.sourceUrl} · {e.sourceDomain}
  2. ))}
); }